package com.ficus.road.maintain.service;

import com.alibaba.fastjson.JSON;
import com.aliyun.oss.model.OSSObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.ficus.road.maintain.authority.dto.RoleDTO;
import com.ficus.road.maintain.authority.service.RoleService;
import com.ficus.road.maintain.authority.service.UserService;
import com.ficus.road.maintain.authority.utils.SecurityUtils;
import com.ficus.road.maintain.authority.vo.LoginResponse;
import com.ficus.road.maintain.config.DiseaseConfig;
import com.ficus.road.maintain.core.infrastructure.mongo.MongoDbConfig;
import com.ficus.road.maintain.core.model.*;
import com.ficus.road.maintain.core.model.oss.AliyunOssResult;
import com.ficus.road.maintain.core.model.taskInfo.AbnormalWorkCheckCDRequest;
import com.ficus.road.maintain.core.model.taskInfo.AbnormalWorkCheckRequest;
import com.ficus.road.maintain.core.model.taskInfo.TaskInfoDetail;
import com.ficus.road.maintain.core.model.taskInfo.TaskInfoResponse;
import com.ficus.road.maintain.core.utils.JsonUtils;
import com.ficus.road.maintain.dao.*;
import com.ficus.road.maintain.dao.xlj.XljWarningDao;
import com.ficus.road.maintain.dto.StatusStatisticResponse;
import com.ficus.road.maintain.third.zh.roadgps.ZHRoadName;
import com.ficus.road.maintain.util.*;
import com.google.common.collect.Lists;
import ficus.car_demo.FrameMeta;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.GroupOperation;
import org.springframework.data.mongodb.core.aggregation.MatchOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

import static com.ficus.road.maintain.util.SsfFileUtil.*;

/**
 * @Author hao.zhou1
 * @Since 2021/4/8 上午11:19
 */
@Service
@Slf4j
public class HitService {

    public static final String defaultRejectedReason = "退单原因尚未明确";
    public static Set<String> crackTypeSet = new HashSet<>() {
        {
            add("PIC_POTHOLES_RESULT_SETTLEMENT");
            add("PIC_CRACK_RESULT_PIC_CRACK");
            add("PIC_CRACK_RESULT_CRACKED");
        }
    };
    private static Map<String, String> carCompanyMap = new HashMap<>();
    private static Map<String, String> companyNameMap = new HashMap<>();
    //
    private final Criteria validStatCri = Criteria
            .where("attrs.source").ne(SourceEnum.MOCK)
            .and("attrs.confirmation.status").is(Confirmation.ConfirmationStatus.CORRECT);
    @Autowired
    private HitDao hitDao;
    @Value("${hit-config.merge-distance}")
    private int mergeDistance;
    @Value("${oss.storeImage}")
    private Boolean storeImage;
    @Autowired
    private BaiduMapClient baiduMapClient;
    @Autowired
    private AliyunOSSUtil aliyunOSSUtil;
    @Autowired
    private DiseaseConfig diseaseConfig;
    @Autowired
    private ZhonghaiClient zhonghaiClient;
    @Autowired
    private ZHRoadName zhRoadName;
    @Autowired
    private OperationLogDao operationLogDao;
    @Autowired
    private RoadSectionDao roadSectionDao;
    @Autowired
    private MongoDbConfig mongoDbConfig;
    @Autowired
    private InspectPictureDao inspectPictureDao;
    @Value("${hit-config.total-distance}")
    private float totalDistance;

    @Value("${oss.disease-prefix}")
    private String prefix;

    @Value("${present.flag}")
    private Boolean present;

    @Autowired
    private CarDao carDao;
    @Autowired
    private CompanyDao companyDao;
    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private XljWarningDao xljWarningDao;
    @Autowired
    private MechanicalCleaningDao mechanicalCleaningDao;
    @Autowired
    private HitAllFactoryDao hitAllFactoryDao;
    @Autowired
    private MaintenanceTmOutDao maintenanceTmOutDao;

    @Autowired
    private FailedThirdRequestDao failedThirdRequestDao;

    @Autowired
    private ZHCDClient zhcdClient;

    @PostConstruct
    public void init() {
        List<Car> all = carDao.findAll();
        carCompanyMap = all.stream().collect(Collectors.toMap(Car::getId, Car::getHandleCompany));
        List<Company> all1 = companyDao.findAll();
        companyNameMap = all1.stream().filter(item -> ObjectUtils.isNotEmpty(item.getName())).collect(Collectors.toMap(Company::getId, Company::getName));
    }

    public String createHit(Hit hit) {
        hit.setId(hit.getAttrs().getCarId() + IdGeneratorUtil.generatorId());
        Hit result = hitDao.saveOrUpdate(hit);
        return result.getId();
    }

    public void createHits(List<Hit> hits) {
        //新的逻辑，判断是否有历史告警
        hits.forEach(hit -> {
            //生成病害图并上传到OOS
            if (ObjectUtils.isNotEmpty(hit)) {
                // 井盖高差控制病害数量, 只把发过来的病害的10%
                if ("PIC_MANHOLE_COVER_DISLOCATION_RESULT_DISEASE_CLIFF".equals(hit.getType())) {
                    double random = Math.random();
                    if (random > 0.1) {
                        return;
                    }
                }
                String picturePath = hit.getAttrs().getPicturePath();
                String diseasePath = picturePath.replace(PIC, DISEASE);
                String picIndex = hit.getAttrs().getSceneImage();
                String originImageBase64 = null;
                OSSObject file = aliyunOSSUtil.getFile(diseasePath);
                String diseasePicName = prefix + "/" + hit.getAttrs().getCarId() + "/" + sdf.format(new Timestamp(hit.getFirstHitTime() * 1000)) + "/" + hit.getFirstHitTime() + RandomUtils.nextInt(100, 999);
                try {
                    FrameMeta.PicArray picArray = FrameMeta.PicArray.parseFrom(file.getObjectContent());
                    byte[] originImageBytes = picArray.getPics(Integer.parseInt(picIndex)).toByteArray();
                    originImageBase64 = ImageUtil.bytes2Base64(originImageBytes);
                    AliyunOssResult ossResult = aliyunOSSUtil.uploadImg(diseasePicName + ".jpg", originImageBytes);
                    if (ossResult.getCode() == 200) {
                        hit.getAttrs().setSceneImage(ossResult.getUrl());
                        hit.getAttrs().setSceneImageName(ossResult.getName());
                        // 深圳演示
//                        log.info("createInspectPicture in dataProcessor");
//                        createInspectPicture(hit, ossResult.getUrl());
                    } else {
                        log.info("upload image failed, hitId: {}, hitImage64:{}", hit.getId(), originImageBytes);
                        hit.getAttrs().setSceneImage(originImageBase64);
                    }
                } catch (IOException e) {
                    log.error("parse pics met exception:", e);
                } finally {
                    try {
                        file.forcedClose();
                    } catch (IOException e) {
                        log.error("oss client close met Exception:", e);
                    }
                }
                //病害碰撞
                List<Hit> aggregate = aggregate(hit.getAttrs().getGpsLocation(), hit.getType());

                //病害merge
                if (CollectionUtils.isEmpty(aggregate)) {
                    //获取道路名称
                    String roadInfo = "";
                    if (!present) {
//                        roadInfo = zhonghaiClient.getRoadInfo(hit.getAttrs().getGpsLocation());
                        roadInfo = zhRoadName.getRoadName(hit.getAttrs().getGpsLocation());
                    }
                    if (StringUtils.isBlank(roadInfo) || StringUtils.isBlank(roadInfo.split(",")[0])) {
                        String streetName = baiduMapClient.geocoder(hit.getAttrs().getGpsLocation());
                        roadInfo = new StringBuilder(streetName).append(StringUtils.isBlank(roadInfo) ? "" : roadInfo).toString();
                    }
                    hit.getAttrs().setRoadName(roadInfo);
                    String carId = hit.getAttrs().getCarId();
                    String s = carCompanyMap.get(carId);
                    if (StringUtils.isNotBlank(s)) {
                        String s1 = companyNameMap.get(s);
                        if (StringUtils.isNotBlank(s1)) {
                            hit.getAttrs().setHandleCompany(s1);
                            hit.getAttrs().setHandleCompanyCode(s);
                        }
                    }
                    hit.setId(hit.getAttrs().getCarId() + IdGeneratorUtil.generatorId());
                    List<Bbox> bboxList = hit.getSubjects().stream().map(HitSubject::getBbox).collect(Collectors.toList());
                    List<RelBbox> relBboxList = hit.getSubjects().stream().map(HitSubject::getRelBbox).collect(Collectors.toList());
                    byte[] drawFrameImageBytes = ImageUtil.drawFrame(originImageBase64, bboxList, relBboxList);
                    AliyunOssResult drawFrameImageResult = aliyunOSSUtil.uploadImg(diseasePicName + "-draw.jpg", drawFrameImageBytes);
                    if (drawFrameImageResult.getCode() == 200) {
                        hit.getAttrs().setFrameImageUrl(drawFrameImageResult.getUrl());
                    } else {
                        log.info("upload image failed, hitId: {}, hitImage64:{}", hit.getId(), originImageBase64);
                    }
                    hit.setList(Lists.newArrayList(HistoryHit.fromHit(hit)));
                    hit.setCreationTime(System.currentTimeMillis() / 1000);
                    hit.setLastModificationTime(System.currentTimeMillis() / 1000);
                    hitDao.insert(hit);
                } else {
                    // 该病害之前出现过，这里更新之前出现过的病害
                    Hit hitDB = aggregate.get(0);
                    hitDB.setLastModificationTime(System.currentTimeMillis() / 1000);
                    // 大于1个小时，更新一次历史
                    // 小于1小时不存，表示同一天只会存1次历史，且非复核病害才存历史（问题来了，复核过后为什么还会发现这个病害呢？）
                    if (hit.getHitTime() > (hitDB.getList().get(0).getHitTime() + 3600)) {
                        if (hitDB.getAttrs().getStatus().equals(HitStatusEnum.COMPLETE)) {
                            hitDB.getAttrs().getConfirmation().setStatus(Confirmation.ConfirmationStatus.TO_BE_CONFIRMED);
                        }
                        hit.getAttrs().setStatus(hitDB.getAttrs().getStatus());
                        hitDB.getList().add(0, HistoryHit.fromHit(hit));
                    }
                    hitDao.saveOrUpdate(hitDB);
                }
            }
        });
    }

    private void createInspectPicture(Hit hit, String url) {
        GpsLocation gpsLocation = hit.getAttrs().getGpsLocation();
        InspectPicture inspectPicture = InspectPicture.builder()
                .carId(hit.getAttrs().getCarId())
                .url(url)
                .timeStamp(hit.getHitTime())
                .creationTime(new Date())
                .gpsLocation(gpsLocation)
                .location(Lists.newArrayList(gpsLocation.getLongitude(), gpsLocation.getLatitude()))
                .flag(0)
                .build();
        inspectPictureDao.saveOrUpdate(inspectPicture);
    }

    /**
     *  调用中海公路系统接口, 查询流程退单具体原因
     * @return
     */
    public String getHitRejectedReasonFromThird(String taskNum) {
        // 调用公路系统接口 查询流程退单具体原因
        String reason = zhonghaiClient.getReasonFromZhongHaiTaskInfo(taskNum);
        if (reason == null) {
            // 保存失败请求等待重试
            AbnormalWorkCheckRequest abnormalWorkCheckRequest = new AbnormalWorkCheckRequest(taskNum);
            failedThirdRequestDao.saveOrUpdate(abnormalWorkCheckRequest);
            return null;
        } else if (StringUtils.isNotBlank(reason)) {
            return reason;
        }

        // 特殊情况 中海回调的id不符合预期
        if (taskNum.length() < 12) {
            return null;
        }
        String subTaskNum = taskNum.substring(12);
        log.info("调用公路接口返回为空,尝试调用城道中心接口查询案件流程详情, orgId={}, subId={}...",taskNum,subTaskNum);

        reason = zhcdClient.getReasonFromZhongHaiCDTaskInfo(subTaskNum);
        if (reason == null) {
            AbnormalWorkCheckCDRequest abnormalWorkCheckCDRequest = new AbnormalWorkCheckCDRequest(taskNum);
            failedThirdRequestDao.saveOrUpdate(abnormalWorkCheckCDRequest);
            return null;
        }

        return reason;
    }

    public void updateHitStatus(String id, UpdateHitStatusRequest updateHitStatusRequest) {
        Hit hit = hitDao.fetch(id);
        if (ObjectUtils.isNotEmpty(hit)) {
            HitAttrs attrs = hit.getAttrs();
            if (ObjectUtils.isNotEmpty(attrs)) {
                HitStatusEnum hitStatus = attrs.getStatus();
                if (ObjectUtils.isNotEmpty(hitStatus) && (hitStatus.equals(HitStatusEnum.REJECTED) || hitStatus.equals(HitStatusEnum.NOTED)) ) {
                    // 如果当前病害是已退单 或者 已提醒，不接受后续状态的修改
                    log.warn("当前病害: {} 状态为已退单, 不接受后续状态修改", id);
                    return;
                }
            }

            HitStatusEnum status = updateHitStatusRequest.getStatus();
            String flowType = updateHitStatusRequest.getFlowType();
            if (status.equals(HitStatusEnum.COMPLETE) || status.equals(HitStatusEnum.REJECTED)) {
                if (hit.getAttrs().getStatus().equals(status)) {
                    log.warn("回调状态重复，忽略。。。");
//                    return;
                }
                String description = updateHitStatusRequest.getDescription();
                String handlePerson = updateHitStatusRequest.getHandlePerson();
                String resolveImageUrl = updateHitStatusRequest.getResolveImageUrl();
                String handleCompany = updateHitStatusRequest.getHandleCompany();
                String handleCompanyCode = updateHitStatusRequest.getHandleCompanyCode();
                String handleOffice = updateHitStatusRequest.getHandleOffice();
                String handleOfficeCode = updateHitStatusRequest.getHandleOfficeCode();
                String section = updateHitStatusRequest.getSection();
                String sectionCode = updateHitStatusRequest.getSectionCode();
                hit.getAttrs().setStatus(status);
                if (status.equals(HitStatusEnum.COMPLETE))
                    hit.getAttrs().setDescription(description);
                else if (status.equals(HitStatusEnum.REJECTED)) {
                    String reason = getHitRejectedReasonFromThird(id);
                    if (reason == null) {
                        log.error("查询第三方接口获取卷宗流程信息失败,taskNum = {}",id);
                        hit.getAttrs().setDescription(defaultRejectedReason);
                    } else if (!reason.equals("已结案"))
                        hit.getAttrs().setDescription(reason);
                }
                hit.getAttrs().setHandlePerson(handlePerson);
                Integer needConfirm = updateHitStatusRequest.getNeedConfirm();
                List<String> resolveImageUrls = new ArrayList<>();
                String sceneImage = "";
                if (StringUtils.isNotEmpty(resolveImageUrl)) {
                    String[] split = resolveImageUrl.split(",|\\|");
                    resolveImageUrls = Arrays.asList(split);
                    sceneImage = split[0];
                }

                hit.getAttrs().setResolveImageUrl(resolveImageUrls);
                if (StringUtils.isNotBlank(handleCompany)) {
                    hit.getAttrs().setHandleCompany(handleCompany);
                }
                if (StringUtils.isNotBlank(handleCompanyCode)) {
                    hit.getAttrs().setHandleCompanyCode(handleCompanyCode);
                }
                if (StringUtils.isNotBlank(handleOffice)) {
                    hit.getAttrs().setHandleOffice(handleOffice);
                }
                if (StringUtils.isNotBlank(handleOfficeCode)) {
                    hit.getAttrs().setHandleOfficeCode(handleOfficeCode);
                }
                if (StringUtils.isNotBlank(section)) {
                    hit.getAttrs().setSection(section);
                }
                if (StringUtils.isNotBlank(sectionCode)) {
                    hit.getAttrs().setSectionCode(sectionCode);
                }
                if (needConfirm != null) {
                    hit.getAttrs().setNeedConfirm(needConfirm == 1);
                }
                long currentTimeStamp = System.currentTimeMillis();
                if (updateHitStatusRequest.getHandleTime() != null) {
                    hit.getAttrs().setFinishHandleTime(updateHitStatusRequest.getHandleTime());
                }
                hit.setLastModificationTime(currentTimeStamp / 1000);
                if (!hit.getList().get(0).getAttrs().getStatus().equals(status)) {
                    HistoryHit historyHit = HistoryHit.fromHit(hit);
                    historyHit.getAttrs().setSceneImage(sceneImage);
                    historyHit.setSubjects(new ArrayList<>());
                    historyHit.setHitTime(currentTimeStamp / 1000);
                    hit.getList().add(0, historyHit);
                }
                hitDao.saveOrUpdate(hit);
            } else if (status.equals(HitStatusEnum.UNDISPATCH)) {
                String handleCompany = updateHitStatusRequest.getHandleCompany();
                String handleCompanyCode = updateHitStatusRequest.getHandleCompanyCode();
                String handleOffice = updateHitStatusRequest.getHandleOffice();
                String handleOfficeCode = updateHitStatusRequest.getHandleOfficeCode();
                String section = updateHitStatusRequest.getSection();
                String sectionCode = updateHitStatusRequest.getSectionCode();
                if (StringUtils.isNotBlank(handleCompany)) {
                    hit.getAttrs().setHandleCompany(handleCompany);
                }
                if (StringUtils.isNotBlank(handleCompanyCode)) {
                    hit.getAttrs().setHandleCompanyCode(handleCompanyCode);
                }
                if (StringUtils.isNotBlank(handleOffice)) {
                    hit.getAttrs().setHandleOffice(handleOffice);
                }
                if (StringUtils.isNotBlank(handleOfficeCode)) {
                    hit.getAttrs().setHandleOfficeCode(handleOfficeCode);
                }
                if (StringUtils.isNotBlank(section)) {
                    hit.getAttrs().setSection(section);
                }
                if (StringUtils.isNotBlank(sectionCode)) {
                    hit.getAttrs().setSectionCode(sectionCode);
                }
                if (updateHitStatusRequest.getStakeNum() != null) {
                    hit.getAttrs().setStake(updateHitStatusRequest.getStakeNum());
                    //找section,hit.attrs.roadSection
                    findRoadSection(hit, updateHitStatusRequest.getStakeNum());
                }
                hitDao.saveOrUpdate(hit);
            } else if (status.equals(HitStatusEnum.PROCESSING) || status.equals(HitStatusEnum.DISPATCHED)) {
                if (StringUtils.isNotBlank(updateHitStatusRequest.getStakeNum())) {
                    hit.getAttrs().setStake(updateHitStatusRequest.getStakeNum());
                    // 找section,hit.attrs.roadSection
                    findRoadSection(hit, updateHitStatusRequest.getStakeNum());
                }
                if (updateHitStatusRequest.getHandleTime() != null) {
                    hit.getAttrs().setStartHandleTime(updateHitStatusRequest.getHandleTime());
                }
                if (status.equals(HitStatusEnum.DISPATCHED)) {
                    if (!hit.getAttrs().getStatus().equals(HitStatusEnum.DISPATCHED)) {
                        HistoryHit historyHit = HistoryHit.fromHit(hit);
                        historyHit.getAttrs().setStatus(HitStatusEnum.DISPATCHED);
                        hit.getList().add(0, historyHit);
                    }
                    hit.getAttrs().setStatus(HitStatusEnum.DISPATCHED);
                    hit.setLastModificationTime(System.currentTimeMillis() / 1000);
                    if (ObjectUtils.isNotEmpty(flowType) && "1".equals(flowType)) {
                        // 提醒件
                        attrs.setFlowType("1");
                        hitDao.saveOrUpdate(hit);
                    }
                }
                hitDao.saveOrUpdate(hit);
            } else {
                log.warn("暂时不支持此类状态的更改, {}", status);
            }
        } else {
            log.error("zhonghai's data not exist in yitu db!,id={}, detail={}", id, JSON.toJSONString(updateHitStatusRequest));
        }
    }

    public void findRoadSection(Hit hit, String stake) {
        if (StringUtils.isNotBlank(hit.getAttrs().getRoadName())) {
            String[] roadName = hit.getAttrs().getRoadName().split(",");
            Double stakeDouble = Double.parseDouble(stake);
            Query query = new Query(Criteria.where("road_name").is(roadName[0])
                    .and("start_stake").lte(stakeDouble).and("end_stake").gte(stakeDouble));
            RoadSection roadSection = roadSectionDao.findOne(query);
            if (ObjectUtils.isNotEmpty(roadSection)) {
                //更新病害的字段
                hit.getAttrs().setRoadSection(roadSection.getId());
                //更新病害密度
//                Query queryByRoadSection = new Query(Criteria.where("attrs.roadSection").is(roadSection));
//                long count = hitDao.count(queryByRoadSection);
//                String secLen = roadSection.getSecLen();
//                if (ObjectUtils.isNotEmpty(roadSection.getSecLen())) {
//                    long len = Long.parseLong(secLen);
//                    roadSection.setDiseaseDensity(String.valueOf(count / len));
//                    roadSectionDao.saveOrUpdate(roadSection);
//                }
            }
        }
    }

    public MensalDiseaseStatisticResponse queryMensalDiseaseStatistic(String id) {
        MensalDiseaseStatisticResponse mensalDiseaseStatisticResponse = new MensalDiseaseStatisticResponse();
        //一个月前的时间
        Date monthAgo = DateUtils.addDays(new Date(), -30);
        //将时间设置为一个月前的0点0分0秒
        DateUtils.setHours(monthAgo, 0);
        DateUtils.setSeconds(monthAgo, 0);
        DateUtils.setMinutes(monthAgo, 0);
        MensalDiseaseClassification lastMonthDiseaseClassification = queryMensalDiseaseHits(id, monthAgo, new Date());
        //2个月前的时间
        Date twoMonthAgo = DateUtils.addDays(monthAgo, -30);
        MensalDiseaseClassification last2MonthDiseaseClassification = queryMensalDiseaseHits(id, twoMonthAgo, monthAgo);
        long sumDistance = (long) totalDistance;
        if (StringUtils.isNotBlank(id)) {
            List<Car> cars = carDao.findByQuery(new Query(Criteria.where("handle_company").is(id)));
            sumDistance = cars.stream().mapToInt(item -> item.getShouldCover().intValue()).sum();
        }
        float cleanCount = lastMonthDiseaseClassification.getCleanCount();
        float cleanCount2 = last2MonthDiseaseClassification.getCleanCount();
        float orderCount = lastMonthDiseaseClassification.getOrderCount();
        float orderCount2 = last2MonthDiseaseClassification.getOrderCount();
        float securityCount = lastMonthDiseaseClassification.getSecurityCount();
        float securityCount2 = last2MonthDiseaseClassification.getSecurityCount();
        mensalDiseaseStatisticResponse.setCleanCount(cleanCount / sumDistance);
        mensalDiseaseStatisticResponse.setOrderCount(orderCount / sumDistance);
        mensalDiseaseStatisticResponse.setSecurityCount(securityCount / sumDistance);

        mensalDiseaseStatisticResponse.setCleanRate(cleanCount2 != 0 ? (cleanCount - cleanCount2 / cleanCount2) : 0);
        mensalDiseaseStatisticResponse.setOrderRate(orderCount2 != 0 ? (orderCount - orderCount2 / orderCount2) : 0);
        mensalDiseaseStatisticResponse.setSecurityRate(securityCount2 != 0 ? (securityCount - securityCount2 / securityCount2) : 0);
        return mensalDiseaseStatisticResponse;
    }

    public MensalDiseaseClassification queryMensalDiseaseHits(String id, Date startDate, Date endDate) {
        MensalDiseaseClassification mensalDiseaseClassification = new MensalDiseaseClassification();
        Criteria criteria = Criteria.where("first_hit_time").gt(startDate.getTime() / 1000).lt(endDate.getTime() / 1000).and("attrs.confirmation.status").is(Confirmation.ConfirmationStatus.CORRECT);
        Query query = new Query(criteria);
        query.fields().include("type");
        query.fields().include("hit_time");
        query.fields().include("classification");
        //获取当前用户的角色信息
        UserDetails currentUser = SecurityUtils.getCurrentUser();
        LoginResponse loginResponse = userService.queryLoginInfoByUsername(currentUser.getUsername());
        List<RoleDTO> roleDTOS = roleService.queryRolesById(loginResponse.getRoleIdList());
        Set<String> roleNames = roleDTOS.stream().map(RoleDTO::getRoleName).collect(Collectors.toSet());
        long sumDistance = 20L;
        if (!roleNames.contains("管理员") && !roleNames.contains("道运中心") && !roleNames.contains("市道运局")) {
            //如果是管养公司,只统计自己车辆检查出的病害
            query.addCriteria(Criteria.where("attrs.car_id").in(loginResponse.getCarIdList()));
        } else {
            //管理员或者道运中心
            if (StringUtils.isNotBlank(id)) {
                //找到管养公司的车辆
                List<Car> cars = carDao.findByQuery(new Query(Criteria.where("handle_company").is(id)));
                List<String> carIds = cars.stream().map(Car::getId).collect(Collectors.toList());
                query.addCriteria(Criteria.where("attrs.car_id").in(carIds));
            }
        }
        List<Hit> hits = hitDao.findByQuery(query);
        int orderCount = 0;
        int securityCount = 0;
        int cleanCount = 0;
        for (Hit hit : hits) {
            String classification = hit.getClassification();
            switch (classification) {
                case "ORDER":
                    orderCount++;
                    break;
                case "SECURITY":
                    securityCount++;
                    break;
                case "CLEAN":
                    cleanCount++;
                    break;
                default:
                    break;
            }
        }
        mensalDiseaseClassification.setCleanCount(cleanCount);
        mensalDiseaseClassification.setOrderCount(orderCount);
        mensalDiseaseClassification.setSecurityCount(securityCount);
        return mensalDiseaseClassification;
    }

    public Hit getHit(String id) {
        Hit result = hitDao.fetch(id);
        //病害派单时间，病害什么时候派单？
        Long dispatchTime = result.getDispatchTime();
        //业务解释一下？
        if (dispatchTime != null) {
            long currentTime = System.currentTimeMillis() / 1000;
            int discoverTime = (int) (currentTime - dispatchTime);
            Integer limitHour = result.getAttrs().getLimitHour();
            if (limitHour > 0) {
                int restHour = limitHour - discoverTime / 3600;
                //更新病害剩余处理时间？
                result.getAttrs().setRestResolveHour(restHour);
            }
        }
        return result;
    }

    public void updateMockHits(List<MockHitUpdateRequest> mockHitUpdateRequests) {
        for (MockHitUpdateRequest mockHitUpdateRequest : mockHitUpdateRequests) {
            Hit hit = hitDao.fetch(mockHitUpdateRequest.getId());
            Long hitTime = mockHitUpdateRequest.getHitTime();
            String carId = mockHitUpdateRequest.getCarId();
            Boolean isTop = mockHitUpdateRequest.getIsTop();
            String resolveImage = mockHitUpdateRequest.getResolveImage();
            Long resolveTime = mockHitUpdateRequest.getResolveTime();
            hit.setFirstHitTime(hitTime);
            hit.setHitTime(hitTime);
            List<HistoryHit> list = hit.getList();
            hit.getList().get(list.size() - 1).setHitTime(hitTime);
            if (StringUtils.isNotBlank(resolveImage) || ObjectUtils.isNotEmpty(resolveTime)) {
                if (list.size() == 1) {
                    HistoryHit historyHit = SerializationUtils.clone(list.get(0));
                    historyHit.setHitTime(resolveTime);
                    historyHit.getAttrs().setSceneImage(resolveImage);
                    historyHit.setSubjects(new ArrayList<>());
                    hit.getList().add(0, historyHit);
                    hit.getAttrs().setResolveImageUrl(Lists.newArrayList(resolveImage));
                } else {
                    hit.getAttrs().setResolveImageUrl(Lists.newArrayList(resolveImage));
                    hit.getList().get(0).getAttrs().setSceneImage(resolveImage);
                    hit.getList().get(0).setHitTime(resolveTime);
                }
            }
            hit.getAttrs().setCarId(carId);
            hit.getAttrs().setIsTop(isTop);
            hitDao.saveOrUpdate(hit);
        }
    }

    public void delete(String id) {
        Hit hit = hitDao.fetch(id);
        //将告警信息的状态置为已删除
        hit.getAttrs().getConfirmation().setStatus(Confirmation.ConfirmationStatus.DELETE);
        hit.getAttrs().getConfirmation().setConfirmTime(System.currentTimeMillis() / 1000);
        hit.setLastModificationTime(System.currentTimeMillis() / 1000);
        hitDao.saveOrUpdate(hit);
    }


    /**
     * 根据病害类型统计当前月/历史
     * 的病害数量
     */
    public List<DiseaseTypeCount> statisticByType() {
//        Criteria criteria = Criteria.where("first_hit_time").gte(monthly.getTime() / 1000).and("attrs.confirmation.status").is("CORRECT")
//                .and("attrs.status").in(HitStatusEnum.COMPLETE.toString(), HitStatusEnum.CONFIRMED.toString(), HitStatusEnum.CONFIRMFAILED.toString());
        Date firstDayOfMonth = MyDateUtil.getFirstDayOfMonth();
        Date startOfDay = MyDateUtil.getStartOfDay(new Date());
        // 统计智能车发现的病害
        Criteria dayCriteria = Criteria.where("first_hit_time").gte(startOfDay.getTime() / 1000).and("attrs.confirmation.status").is("CORRECT");
        Criteria monthCriteria = Criteria.where("first_hit_time").gte(firstDayOfMonth.getTime() / 1000).and("attrs.confirmation.status").is("CORRECT");
        Criteria totalCriteria = Criteria.where("attrs.confirmation.status").is("CORRECT").and("attrs.status").ne("NEW");
        MatchOperation dayMatch = Aggregation.match(dayCriteria);
        MatchOperation monthMatch = Aggregation.match(monthCriteria);
        MatchOperation totalMatch = Aggregation.match(totalCriteria);
        GroupOperation groupOperation = Aggregation.group("type").first("type").as("type").count().as("history_count");
        Aggregation dayAggregation = Aggregation.newAggregation(dayMatch, groupOperation);
        Aggregation monthAggregation = Aggregation.newAggregation(monthMatch, groupOperation);
        Aggregation totalAggregation = Aggregation.newAggregation(totalMatch, groupOperation);
        List<DiseaseTypeCount> dayResult = hitDao.findAggregateList(dayAggregation, DiseaseTypeCount.class);
        List<DiseaseTypeCount> monthResult = hitDao.findAggregateList(monthAggregation, DiseaseTypeCount.class);
        List<DiseaseTypeCount> totalResult = hitDao.findAggregateList(totalAggregation, DiseaseTypeCount.class);
        List<DiseaseTypeCount> result = new ArrayList<>(totalResult);
        for (DiseaseTypeCount res : result) {
            Optional<DiseaseTypeCount> month = monthResult.stream()
                    .filter(item -> ObjectUtils.isNotEmpty(item.getType()) && item.getType().equals(res.getType())).findFirst();
            if (month.isPresent()) {
                res.setCount(month.get().getHistoryCount());
            } else {
                res.setCount(0);
            }
            Optional<DiseaseTypeCount> day = dayResult.stream()
                    .filter(item -> ObjectUtils.isNotEmpty(item.getType()) && item.getType().equals(res.getType())).findFirst();
            if (day.isPresent()) {
                res.setTodayCount(day.get().getHistoryCount());
            } else {
                res.setTodayCount(0);
            }
        }

        // 统计中海的病害
        Query xljMonthQuery = new Query(Criteria.where("create_time").gte(firstDayOfMonth));
        Query xljDayQuery = new Query(Criteria.where("create_time").gte(startOfDay));
        int dayXljWarningCount = (int) xljWarningDao.count(xljDayQuery);
        int monthXljWarningCount = (int) xljWarningDao.count(xljMonthQuery);
        int totalXljWarningCount = (int) xljWarningDao.count(new Query());
        result.add(DiseaseTypeCount.builder()
                .type(AllFactoryTypeEnum.OVERPASS.getType())
                .todayCount(dayXljWarningCount)
                .count(monthXljWarningCount)
                .historyCount(totalXljWarningCount).build());

        // 全要素治理数量统计
        String startOfDayStr = MyDateUtil.dateFormatStr(startOfDay, "yyyy-MM-dd");
        String firstDayOfMonthStr = MyDateUtil.dateFormatStr(firstDayOfMonth, "yyyy-MM-dd");
        Query queryMCDay = new Query(Criteria.where("is_ok").is("不合格").and("date").gte(startOfDayStr));
        Query queryMCMonth = new Query(Criteria.where("is_ok").is("不合格").and("date").gte(firstDayOfMonthStr));
        int dayCleaning = (int) mechanicalCleaningDao.count(queryMCDay);
        int monthCleaning = (int) mechanicalCleaningDao.count(queryMCMonth);
        int totalCleaning = (int) mechanicalCleaningDao.count(new Query());
        result.add(DiseaseTypeCount.builder()
                .type(AllFactoryTypeEnum.MECHANICAL_CLEANING.getType())
                .todayCount(dayCleaning)
                .count(monthCleaning)
                .historyCount(totalCleaning).build());

        Criteria dayAllFactoryCriteria = Criteria.where("first_hit_time").gte(startOfDay.getTime() / 1000);
        Criteria monthAllFactoryCriteria = Criteria.where("first_hit_time").gte(firstDayOfMonth.getTime() / 1000);
        Aggregation dayAllFactoryAggregation = Aggregation.newAggregation(Aggregation.match(dayAllFactoryCriteria), groupOperation);
        Aggregation monthAllFactoryAggregation = Aggregation.newAggregation(Aggregation.match(monthAllFactoryCriteria), groupOperation);
        Aggregation totalAllFactoryAggregation = Aggregation.newAggregation(groupOperation);
        List<DiseaseTypeCount> allFactoryDayList = hitAllFactoryDao.findAggregateList(dayAllFactoryAggregation, DiseaseTypeCount.class);
        List<DiseaseTypeCount> allFactoryMonthList = hitAllFactoryDao.findAggregateList(monthAllFactoryAggregation, DiseaseTypeCount.class);
        List<DiseaseTypeCount> allFactoryTotalList = hitAllFactoryDao.findAggregateList(totalAllFactoryAggregation, DiseaseTypeCount.class);
        for (DiseaseTypeCount res : allFactoryTotalList) {
            Optional<DiseaseTypeCount> day = allFactoryDayList.stream().filter(item -> item.getType().equals(res.getType())).findFirst();
            Optional<DiseaseTypeCount> month = allFactoryMonthList.stream().filter(item -> item.getType().equals(res.getType())).findFirst();
            int dayCount = 0, monthCount = 0;
            if (day.isPresent()) {
                dayCount = day.get().getHistoryCount();
            }
            if (month.isPresent()) {
                monthCount = month.get().getHistoryCount();
            }
            result.add(DiseaseTypeCount.builder().type(res.getType())
                    .todayCount(dayCount)
                    .count(monthCount)
                    .historyCount(res.getHistoryCount())
                    .build());
        }

        return result.stream().filter(item -> ObjectUtils.isNotEmpty(item.getType())).collect(Collectors.toList());
    }


    /**
     * 复核告警
     *
     * @param id
     * @param confirmHitRequest
     */
    public void reConfirm(String id, ConfirmHitRequest confirmHitRequest) {
        Hit hit = hitDao.fetch(id);
        if (hit.getAttrs().getStatus().equals(HitStatusEnum.COMPLETE) || hit.getAttrs().getStatus().equals(HitStatusEnum.REJECTED)) {
            if (confirmHitRequest.getStatus().equals(ConfirmHitRequest.ConfirmStatus.CORRECT)) {

            } else if (confirmHitRequest.getStatus().equals(ConfirmHitRequest.ConfirmStatus.INCORRECT)) {

            }
        }
        hitDao.saveOrUpdate(hit);
    }

    public List<Hit> queryHits(int start, int limit, long startTime, long endTime, List<String> carIds) {
        Criteria criteria = Criteria.where("first_hit_time").gt(startTime).lte(endTime)
                .and("attrs.confirmation.status").is(Confirmation.ConfirmationStatus.CORRECT.toString())
                .and("attrs.car_id").in(carIds);
        return hitDao.findByQuery(new Query(criteria).skip(start).limit(limit));
    }

    /**
     * 查询演示类型告警
     *
     * @param start
     * @param limit
     * @return
     */
    public List<Hit> queryMockHits(int start, int limit) {
        Criteria criteria = Criteria.where("attrs.confirmation.status").is(Confirmation.ConfirmationStatus.CORRECT.toString())
                .and("attrs.source").is(SourceEnum.MOCK.toString());
        return hitDao.findByQuery(new Query(criteria).skip(start).limit(limit));
    }

    public List<Hit> queryHits(Query query) {
        query.with(new Sort(Sort.Direction.DESC, "attrs.is_top")).with(new Sort(Sort.Direction.DESC, "first_hit_time"));
        List<Hit> eventHitCommonDOS = hitDao.findByQuery(query);
        long currentTime = System.currentTimeMillis() / 1000;
        eventHitCommonDOS.forEach(item -> {
            Long dispatchTime = item.getDispatchTime();
            if (dispatchTime != null) {
                int discoverTime = (int) (currentTime - dispatchTime);
                Integer limitHour = item.getAttrs().getLimitHour();
                if (limitHour > 0) {
                    int restHour = limitHour - discoverTime / 3600;
                    item.getAttrs().setRestResolveHour(restHour);
                }
            }
        });
        return eventHitCommonDOS;
    }

    public int countHits(Query query) {
//        List<Hit> eventHitCommonDOS = hitDao.findHitBySmart(query);
//        return eventHitCommonDOS.size();
        return (int) hitDao.count(query);
    }

    /**
     * 根据病害类型，找到未完成的状态的病害。 完成状态的病害审核伟通过，或者已复核，或者已拒绝。
     *
     * @param gpsLocation
     * @param type
     * @return
     */
    public List<Hit> aggregate(GpsLocation gpsLocation, String type) {
        Point point = new Point(gpsLocation.getLongitude(), gpsLocation.getLatitude());
        List<AggregationOperation> aggregationList = new ArrayList<>();
        if (crackTypeSet.contains(type)) {
            aggregationList.add(new GeoNearDocument(new Query(Criteria.where("type").in(crackTypeSet)
                    .and("first_hit_time").gt(DateUtils.addDays(new Date(), -7).getTime() / 1000)
                    .and("attrs.status").in(HitStatusEnum.NEW, HitStatusEnum.DISPATCHED, HitStatusEnum.UNDISPATCH, HitStatusEnum.SEND, HitStatusEnum.PROCESSING, HitStatusEnum.COMPLETE, HitStatusEnum.CONFIRMFAILED)
                    .and("attrs.confirmation.status").in(Confirmation.ConfirmationStatus.CORRECT, Confirmation.ConfirmationStatus.TO_BE_CONFIRMED)), point, "location", mergeDistance));
        } else {
            aggregationList.add(new GeoNearDocument(new Query(Criteria.where("type").is(type)
                    .and("first_hit_time").gt(DateUtils.addDays(new Date(), -7).getTime() / 1000)
                    .and("attrs.status").in(HitStatusEnum.NEW, HitStatusEnum.DISPATCHED, HitStatusEnum.UNDISPATCH, HitStatusEnum.SEND, HitStatusEnum.PROCESSING, HitStatusEnum.COMPLETE, HitStatusEnum.CONFIRMFAILED)
                    .and("attrs.confirmation.status").in(Confirmation.ConfirmationStatus.CORRECT, Confirmation.ConfirmationStatus.TO_BE_CONFIRMED)), point, "location", mergeDistance));
        }
        Aggregation agg = Aggregation.newAggregation(
                aggregationList);
        List<Hit> aggregate = hitDao.aggregate(agg);
        return aggregate;
    }

    public List<Hit> findHitBySmart(Query query) {
        // 暂时筛选2022-1月到现在的数据，便于汇报
        int year = MyDateUtil.getYear(new Date());
        query.addCriteria(Criteria.where("first_hit_time").gte(MyDateUtil.strParseDate(year + "-01-01", "yyyy-MM-dd").getTime() / 1000));
        query.addCriteria(validStatCri);
        query.with(new Sort(Sort.Direction.DESC, "first_hit_time"));
        List<Hit> hits = hitDao.findByQuery(query);
        return hits;
    }

    public long countHitSmart(Query query){
        int year = MyDateUtil.getYear(new Date());
        query.addCriteria(Criteria.where("first_hit_time").gte(MyDateUtil.strParseDate(year + "-01-01", "yyyy-MM-dd").getTime() / 1000));
        query.addCriteria(validStatCri);
        return hitDao.count(query);
    }

    public List<Hit> findForSmartRoad(Query query) {
//        query.addCriteria(Criteria.where("first_hit_time").gte((DateUtils.addDays(new Date(), -7).getTime() / 1000)));
        query.addCriteria(validStatCri);
        List<Hit> hits = hitDao.findByQuery(query);
        return hits;
    }

    public List<Hit> queryDailyBySmartRoadType(Query query) {
        // 先把今年数据查出来
//        query.addCriteria(Criteria.where("first_hit_time").gte(MyDateUtil.strParseDate("2022-01-01", "yyyy-MM-dd").getTime() / 1000));
        query.addCriteria(validStatCri);
        List<Hit> hits = hitDao.findByQuery(query);
        return hits;
    }

    public List<StatusStatisticResponse> statusStatistic(long startTime, long endTime, List<String> types) {
        Criteria criteria = Criteria.where("first_hit_time").gte(startTime).lt(endTime)
                .and("attrs.confirmation.status").is(Confirmation.ConfirmationStatus.CORRECT);
        if (CollectionUtils.isNotEmpty(types)) {
            criteria.and("type").in(types);
        }

        MatchOperation matchOperation = Aggregation.match(criteria);
        GroupOperation groupOperation = Aggregation.group("attrs.status").first("attrs.status").as("type").count().as("count");
        Aggregation aggregation = Aggregation.newAggregation(matchOperation, groupOperation);
        List<StatusStatisticResponse> list = hitDao.findAggregateList(aggregation, StatusStatisticResponse.class);
        Map<String, Long> map = list.stream().collect(Collectors.toMap(StatusStatisticResponse::getType, StatusStatisticResponse::getCount));
        StatusStatisticResponse newType = StatusStatisticResponse.builder().type(HitStatusEnum.NEW.toString()).count(0L).build();
        StatusStatisticResponse disType = StatusStatisticResponse.builder().type(HitStatusEnum.DISPATCHED.toString()).count(0L).build();
        StatusStatisticResponse comType = StatusStatisticResponse.builder().type(HitStatusEnum.COMPLETE.toString()).count(0L).build();
        StatusStatisticResponse conType = StatusStatisticResponse.builder().type(HitStatusEnum.CONFIRMED.toString()).count(0L).build();
        StatusStatisticResponse failType = StatusStatisticResponse.builder().type(HitStatusEnum.CONFIRMFAILED.toString()).count(0L).build();
        StatusStatisticResponse noteType = StatusStatisticResponse.builder().type(HitStatusEnum.NOTED.toString()).count(0L).build();
        List<StatusStatisticResponse> result = new ArrayList<>();
        result.add(newType);
        result.add(disType);
        result.add(comType);
        result.add(conType);
        result.add(failType);
        result.add(noteType);
        map.forEach((type, count) -> {
            if (type.equals(HitStatusEnum.NEW.toString())) {
                newType.setCount(newType.getCount() + count);
            } else if (type.equals(HitStatusEnum.DISPATCHED.toString())) {
                newType.setCount(newType.getCount() + count);
                disType.setCount(disType.getCount() + count);
            } else if (type.equals(HitStatusEnum.COMPLETE.toString()) || type.equals(HitStatusEnum.REJECTED.toString())) {
                newType.setCount(newType.getCount() + count);
                disType.setCount(disType.getCount() + count);
                comType.setCount(comType.getCount() + count);
            } else if (type.equals(HitStatusEnum.CONFIRMED.toString())) {
                newType.setCount(newType.getCount() + count);
                disType.setCount(disType.getCount() + count);
                comType.setCount(comType.getCount() + count);
                conType.setCount(conType.getCount() + count);
            } else if (type.equals(HitStatusEnum.CONFIRMFAILED.toString())) {
                newType.setCount(newType.getCount() + count);
                disType.setCount(disType.getCount() + count);
                comType.setCount(comType.getCount() + count);
                conType.setCount(conType.getCount() + count);
                failType.setCount(failType.getCount() + count);
            } else if (type.equals(HitStatusEnum.NOTED.toString())) {
                newType.setCount(newType.getCount() + count);
                noteType.setCount(noteType.getCount() + count);
            }
        });
        return result;
    }

    public Hit saveOrUpdate(Hit hit) {
        return hitDao.saveOrUpdate(hit);
    }

    public List<Hit> findCorrectHitInTime(Date startDate, Date endDate) {
        Query query = new Query(Criteria.where("first_hit_time").gte(startDate.getTime() / 1000)
                .lte(endDate.getTime() / 1000)
                .and("attrs.confirmation.status").is(Confirmation.ConfirmationStatus.CORRECT.toString()));
        List<Hit> byQuery = hitDao.findByQuery(query);
        return byQuery;
    }
}
